Check filter structure, field name and type, and index existence
An empty result from a filter query is almost always a filter problem, not a data problem. The first thing to check is the filter structure: must, should, and must_not have very different semantics, and swapping them or nesting them incorrectly produces an empty result. must is a logical AND - every condition must match. should is a logical OR - at least one must match. must_not is a logical NOT - none may match. A common mistake is to put two conditions in must when the intent was OR, which requires both to match and therefore returns nothing when no point satisfies both. The second thing to check is the field name and type. Payload fields are case-sensitive and exact-match. Filtering on tenant_id when the payload stores tenantId, or filtering on a string value when the payload stores an integer, will return nothing without erroring. The third is whether a payload index exists on the field. Qdrant can filter on unindexed fields (it will scan), but if you created an index with a specific schema (keyword, integer, etc.), filtering with a value that does not match the schema can produce unexpected results.
The mechanism is that Qdrant evaluates the filter as a predicate over each point's payload. If the predicate is logically impossible or the field lookup fails, no point matches, and the query returns an empty list without an error. This is important: an empty result is not an error signal, it is a legitimate result, and Qdrant does not distinguish between 'the filter matched nothing' and 'the filter was malformed but evaluated to false'. So the diagnosis requires you to inspect the payload data directly and verify that a point you expect to match actually has the field and value you are filtering on. The fastest way to do this is to remove the filter entirely and retrieve a few points, then inspect their payloads to confirm the exact field names and types. The second fastest is to test the filter in isolation without the vector query, using a scroll operation, which removes the vector search from the equation.
must vs should vs must_not: verify the logical structure matches your intent.
Field name: exact match and case-sensitive; check for typos and naming conventions.
Field type: string vs integer vs boolean vs datetime; a type mismatch silently returns nothing.
Value format: dates, enums, and IDs often need exact string formatting.
Nested payloads: filtering on a nested field requires the correct dot notation.
Index schema: filtering with a value that does not match the index schema can fail to match.
Empty collection or wrong collection: verify the query is hitting the collection you think it is.
Deleted points: tombstones may still exist but are excluded from results.
The trade-off when debugging is between removing the filter to confirm the data exists and keeping the filter to test each condition in isolation. Both are useful: removing the filter verifies the collection has data at all, and testing each condition in isolation pinpoints which condition is the culprit. The common mistake is to assume the filter is correct and look elsewhere - at the vector search, at the collection, at the cluster - when the filter is almost always the cause. The second mistake is to use should when you meant must, or vice versa, and to not notice because the query returns some results that happen to satisfy the intended logic. The third mistake is to filter on a field that was never indexed and to assume that the absence of an index is the problem; it is not, Qdrant will scan, and the empty result is still a filter evaluation issue. The fourth mistake is not checking whether the points you expect to match were ever actually written with the payload field - a bug in the ingest path can produce points without the field, and then no filter will ever match them. Version note: the filter API has evolved, and the query_points API introduced in qdrant-client 1.10 has a different call shape than the older search/scroll APIs. Some filter semantics, especially around nested and array fields, have been refined across releases.
Version-dependent: the filter API and the payload index schema types have changed across releases. The get_collection response includes the payload_schema field in recent versions, which makes it easier to see what indexes exist. The scroll API and the query_points API have different shapes depending on the client version. If you are debugging on an older version, check the exact filter structure and index schema your version supports, because some of the more expressive filter types (nested, geo, range) may not be available.
You filter on a field that you know exists in your data and get no results. List the first three things you would check.
A teammate says the filter is fine because it does not error. Explain why that reasoning is flawed.
Your filter works in development but returns nothing in production. Diagnose the likely causes and describe how you would confirm them.
You filter on a date range and get no results even though points exist in that range. Walk through how you would debug the date format and index schema.
Design a test suite that validates every filter used in your application against a fixture of known points, and describe how you would keep it up to date as the schema evolves.
Your application has 30 different filter combinations. Describe how you would systematically verify that each one returns the expected results and how you would catch a regression.
You are designing a filter abstraction layer for a search system that supports many clients. Describe how you would prevent malformed filters from producing silently empty results.
A customer reports that a filter returns no results for a specific tenant, but other tenants work. Describe your investigation and the possible root causes.